feat: monetization path — sell inference (M1), autoresearch (M2), indexing (M3)#288
Draft
feat: monetization path — sell inference (M1), autoresearch (M2), indexing (M3)#288
Conversation
Add monetize-guide skill (SKILL.md + seller-prompt reference) that teaches Claude Code the end-to-end flow: pre-flight checks, model detection, pricing research via ERC-8004 registry, user-confirmed pricing, sell execution, reconciliation monitoring, and endpoint verification. Add `obol sell probe <name> -n <ns>` command that hits the public tunnel URL and verifies the endpoint returns 402 with valid x402 pricing headers. Closes the feedback loop so Claude can confirm a service is live.
--per-hour was passing the raw hourly price as the per-request charge (e.g., $0.50/hour charged $0.50 per HTTP request). Now approximates using a 5-minute experiment budget: perRequest = perHour * 5/60. Also rewrites worker_api.py to use stdlib http.server (no Flask dep).
…erage Critical: - Fix path traversal in worker_api.py: validate experiment_id with regex - Add GGUF format guard in publish.py before ollama_create() - Run worker container as non-root user in Dockerfile Medium: - Replace manual provenance struct-to-map with JSON round-trip in sell.go - Fix weak test assertion in TestApproximateRequestPriceFromPerHour - Guard int(amount) cast in coordinate.py with try/except - Remove domain-specific default "val_bpb" from CRD metricName field - Guard maxTimeoutSeconds parse in monetize.py Low: - Add provenance propagation test for build_registration_doc - Fix doc type inconsistency in coordination-protocol.md - Assert all 6 Provenance fields in store_test.go # Conflicts: # cmd/obol/sell.go # internal/embed/infrastructure/base/templates/serviceoffer-crd.yaml # internal/embed/skills/autoresearch-coordinator/references/coordination-protocol.md # internal/embed/skills/autoresearch-coordinator/scripts/coordinate.py # internal/embed/skills/autoresearch/SKILL.md # internal/embed/skills/autoresearch/scripts/publish.py # internal/embed/skills/sell/scripts/monetize.py # internal/inference/store_test.go # tests/test_sell_registration_metadata.py
- Add `obol sell probe <name> -n <ns>` — sends unauthenticated request through Traefik to verify the endpoint returns 402 with x402 pricing. - Create flow-06-sell-setup.sh: idempotent ServiceOffer creation - Create flow-07-sell-verify.sh: wait for reconciliation + verify conditions - Create flow-10-anvil-facilitator.sh: Anvil fork + x402-rs facilitator - Create flow-08-buy.sh: EIP-712 sign + paid request through Traefik All flows pass end-to-end: sell → verify → pay → inference (HTTP 200).
- Add skills and domains array fields to ServiceOffer CRD registration schema. The --register-skills CLI flag was rejected by strict CRD validation because the fields were missing. - Fix coordinator.py parse_402_pricing to handle x402 V1 standard format (accepts[] array) in addition to flat top-level fields. Validated: worker ServiceOffer → 402 gate → .well-known → discovery → coordinator probe.
Paths validated against https://github.com/agntcy/oasf (cloned at R&D/oasf/). Skills: - natural_language_processing/text_generation/chat_completion → natural_language_processing/natural_language_generation/text_completion - machine_learning/model_optimization → analytical_skills/model_optimization Domains: - technology/artificial_intelligence → technology/data_science - technology/artificial_intelligence/research → research_and_development/scientific_research Fixed in: monetize.py, coordinate.py, monetize-guide SKILL.md, autoresearch SKILL.md, autoresearch-worker SKILL.md, coordinator references, seller-prompt.md
analytical_skills/model_optimization was invented (doesn't exist in OASF). Closest real path: devops_mlops/model_versioning (validated against agntcy/oasf schema repo at R&D/oasf/). Also confirms prior fixes: - natural_language_processing/natural_language_generation/text_completion - technology/data_science - research_and_development/scientific_research
LiteLLM does NOT auto-append /v1 for openai/ provider routes. WarnAndStripV1Suffix was removing /v1 from api_base before storing in the ConfigMap, causing LiteLLM to hit /chat/completions (404) instead of /v1/chat/completions. Verified against LiteLLM source (gpt_transformation.py:get_complete_url): api_base is passed as-is to the OpenAI SDK base_url, which appends /chat/completions directly. The /v1 must be in api_base. Removed from all 3 call sites: - cmd/obol/model.go (obol model setup custom) - internal/openclaw/openclaw.go (promptForDirectProvider) - internal/openclaw/openclaw.go (promptForCustomProvider)
Adds a new "External LAN Resources" section to the monetize-guide SKILL.md covering the flow for selling GPU servers or inference endpoints on the local network (e.g., DGX Spark running vLLM). The path: obol model setup custom (bridge into LiteLLM) → obol sell http (create ServiceOffer pointing at LiteLLM). Documents that LAN IPs are reachable from k3d without additional config, and that --endpoint must include /v1 since LiteLLM does not auto-append it. Validated end-to-end with Nemotron 3 Super 120B on 2x DGX Spark: - obol model setup custom validates and adds to LiteLLM - obol sell http creates ServiceOffer - Agent heartbeat reconciles in ~90s → all 6 conditions True - 402 gating works locally and through Cloudflare tunnel - .well-known and /skill.md discovery updated automatically
…cherry-pick Cherry-picking PR #265 into feat/monetize-path had conflicts in sell.go that dropped 3 blocks of code from main: 1. obol sell inference — cluster-aware routing: detects k3d cluster, creates K8s Service+Endpoints bridge to host gateway, creates ServiceOffer, auto-starts tunnel via EnsureTunnelForSell() 2. obol sell http — auto-tunnel: calls EnsureTunnelForSell() after creating a ServiceOffer so the endpoint is immediately public 3. obol sell delete — auto-stop tunnel: when the last ServiceOffer is deleted, stops the quick tunnel and removes the storefront Also restores: - NoPaymentGate field on Deployment and GatewayConfig structs - createHostService(), resolveHostIP(), buildInferenceServiceOfferSpec() - net, runtime, strconv, stack imports
LiteLLM PyPI packages 1.82.7 and 1.82.8 contain a malicious .pth file (litellm_init.pth) that exfiltrates environment variables, SSH keys, cloud credentials, and Kubernetes configs to an external endpoint. See: BerriAI/litellm#24512 Our template used the floating tag `main-stable` which could pull a compromised build. Pin to `main-v1.82.3` (confirmed safe, matches the version currently running in our clusters). Never use floating tags for security-sensitive dependencies.
The cherry-pick of PR #265 dropped _build_skill_md() and _publish_skill_md() from monetize.py, along with their 3 call sites in cmd_process. This meant /skill.md would never be created or updated on a fresh cluster. Restores: - _build_skill_md(): generates service catalog markdown from Ready offers - _publish_skill_md(): creates/updates ConfigMap + Deployment + Service + HTTPRoute for the /skill.md endpoint - 3 call sites in cmd_process: 1. Empty skill.md when no offers exist 2. Full skill.md when all offers are Ready 3. Regenerate after reconciliation loop
No tests existed for tunnel state persistence or auto-stop decision logic — this is why the cherry-pick drift went undetected. New tests in tunnel_lifecycle_test.go: - State round-trip (save/load, quick & dns modes) - Missing state file returns (nil, nil) - State overwrite replaces previous - File permissions (0600 for non-secret metadata) - UpdatedAt timestamp refresh on save - tunnelModeAndURL derivation - shouldAutoStopTunnel decision logic (5 cases covering the logic from sell delete: stop quick tunnels when empty, never stop dns) - Exported LoadTunnelState wrapper
This was referenced Mar 27, 2026
bussyjd
added a commit
that referenced
this pull request
Mar 29, 2026
…ures Two issue specifications for carving PR #288 into focused PRs: 1. reth-erc8004-indexer Helm chart - Standalone chart with 3-tier discovery fallback - Reth indexer → BaseScan (native ERC-8004 metadata) → 8004scan - Go DiscoveryClient interface with FallbackClient 2. Autoresearch infrastructure Helm chart - Round-based reward engine with OPOW influence calculation - Anti-monopoly parity formula penalizing concentrated workers - Commit-reveal Merkle proof verification - Escrow settlement via x402 Commerce Payments Protocol (5x-audited Base contracts, zero custom Solidity) - x402-rs implications and contribution path 9 Gherkin feature files (40+ scenarios): - escrow_round_lifecycle.feature (fixed: receiver is per-PaymentInfo, uses RewardDistributor contract as single receiver) - opow_influence_calculation_with_anti_monopoly_parity.feature (fixed: penalty values verified against TIG opow.rs math, added single-challenge and phase-in scenarios) - commit_reveal_work_verification.feature - reward_pool_distribution_across_roles.feature - multi_tier_worker_discovery_with_fallback.feature - end_to_end_autoresearch_round.feature - erc8004_identity_lifecycle.feature (NEW: registration, transfer, deactivation, schema validation during active rounds) - leaderboard_api.feature (NEW: REST API, historical rounds, cumulative earnings) - round_state_continuity.feature (NEW: fund rollover, atomic transitions, worker state reset, on-chain auditability) Cross-reference analysis: - escrow_contract_cross_reference.md (AuthCaptureEscrow contract audit) - FEATURE_REVIEW.md (comprehensive gap analysis vs ERC-8004/x402)
* chore: update CLAUDE.md conventions and fix stale justfile commands - Add Conventions section (conventional commits, branch prefixes, skill reference) - Trim discoverable sections (Key Packages table, file-path tables, Embedded assets) - Keep full LLM Routing, Security, Pitfalls sections - Fix justfile: obol cluster → obol stack (stale command name) * chore: add golangci-lint v2 config and PostToolUse format hook Aligned with ObolNetwork/charon lint stance, adapted for obol-stack: - Disabled linters not applicable to CLI apps (wrapcheck, forbidigo, noctx, testpackage) - Enabled gofumpt + goimports formatters - Added PostToolUse hook to auto-lint Go files on Write|Edit Many linters are disabled with TODO comments for incremental re-enablement. * style: apply gofumpt and goimports formatting across codebase Auto-fix from golangci-lint v2 with gofumpt + goimports formatters. Also removes unused code: sellInfoCommand (cmd/obol/sell.go), pubKeyBytes (internal/tee/attest_stub.go), and fixes dogsled/thelper/govet warnings. * security: re-enable gosec linter and fix all issues - Tighten WriteFile permissions from 0644 to 0600 across 13 files (config, keys, kubeconfig — no reason for group/world read on local CLI) - Exclude G204 (subprocess) and G101 (credential naming) — systemic false positives for a CLI tool that intentionally runs k3d/helm/kubectl - Exclude gosec from internal/testutil/ (test-only helpers) - Nolint G704 (SSRF), G705 (XSS), G122 (TOCTOU), G703 (path traversal) with explanations — all false positives for local config operations * chore: re-enable 10 linters and fix all issues Re-enabled: errcheck, errchkjson, gocritic, nilerr, nilnil, nosprintfhostport, staticcheck, unconvert, wastedassign. Fixes: - errcheck: add _ = for best-effort calls (browser open, process kill, kubectl display, io.Copy to discard) - nilerr: annotate intentional fallback patterns (missing config files, optional features, backward compat defaults) - staticcheck: lowercase error strings (ST1005), rename hasId→hasID (ST1003), nolint deprecated elliptic.Marshal (SA1019) - gocritic: fix appendAssign slice mutation bugs (copy before append), nolint CGo dupSubExpr false positives, exitAfterDefer in main - unconvert: remove 5 unnecessary int() wrappers on cmd.Int() - nosprintfhostport: use net.JoinHostPort in testutil - wastedassign: use var declaration instead of empty init Still disabled: goconst (32), unparam (28) — low priority. * chore: re-enable goconst and unparam — all linters now active - Extract provider name constants (ProviderOllama, ProviderAnthropic, ProviderOpenAI) into internal/model and use across model/openclaw - Extract API key env var constants (envAnthropicAPIKey, envOpenAIAPIKey) - Extract OS name constants in dns/resolver, update status messages in update package, version unknownValue constant - Add isDevMode() helper in config to eliminate repeated "true" comparison - Simplify buildLocalManagedSecretYAML: remove always-nil error return and unused hostname parameter - Exclude goconst and unparam from test files (test fixtures, mock handler signatures) - Nolint validateInstallOptions networkName — extensibility param All linters from .golangci.yml are now active with zero issues. * fix: address PR review feedback on lint config and hook - Set fix: false in .golangci.yml — lint runs are now non-mutating by default, suitable for CI. Use --fix flag explicitly when wanted. - Hook: use golangci-lint via PATH instead of hardcoded ~/.local/bin/ - Hook: use `golangci-lint fmt "$f"` to scope formatting to the edited file only, avoiding unrelated rewrites across the package --------- Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
# Conflicts: # cmd/obol/sell.go
…netize-path # Conflicts: # cmd/obol/model.go # cmd/obol/network.go # cmd/obol/sell.go # internal/openclaw/openclaw.go # internal/tunnel/tunnel.go
This was referenced Mar 29, 2026
- Add balance guard to buy.py — exits on insufficient USDC unless --force, tells the agent which wallet to fund - Add end-to-end "Full Buy Flow" section to buy-inference SKILL.md covering discover → probe → buy → use paid/<model> → monitor/refill - Add "After Discovery" cross-reference from discovery SKILL.md to buy-inference - Add Docker image fallback for openclaw CLI install in obolup.sh for npm-deprived environments (ARM64 DGX Spark validated) Validated full buyer→seller x402 commerce loop on DGX Spark (Base Sepolia): seller registers on ERC-8004, obol-agent discovers via registry, probes 402, pre-signs ERC-3009 auths, paid/qwen3.5:9b routes through x402-buyer sidecar.
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Consolidates PRs #265, #269, #279, #287 into a single integration branch with validated monetization paths. Each milestone was validated with ralph-loop autoresearch (5 iterations, real user paths, real x402 payments).
Supersedes: #265, #269, #279, #287 (all closed)
Three Monetization Paths
All three use the same ServiceOffer CRD, same 6-stage reconciliation, same x402 payment rails, same ERC-8004 discovery.
What's New
monetize-guideskillobol sell probeautoresearchskillautoresearch-coordinatorskillautoresearch-workerskillreth-erc8004-indexer--register-skills/--register-domainsnow worksparse_402_pricinghandlesaccepts[]arraybuy.py, end-to-end SKILL.md flowobolup.shfalls back to Docker image when npm unavailableBuy-Side Hardening & ARM64 Validation
Validated the full buyer→seller x402 commerce loop on NVIDIA DGX Spark (ARM64, Base Sepolia):
obol sell http→ serviceoffer-controller reconciles to Ready → x402 gate returns 402 → ERC-8004 on-chain registrationbuy.py probe→buy.py buypre-signs ERC-3009 auths →paid/<model>routes through x402-buyer sidecar → paid inference succeedsChanges:
buy.py: exits on insufficient USDC balance unless--force(was silent warning)buy-inference/SKILL.md: added "Full Buy Flow" section covering discover → probe → buy → usepaid/<model>→ monitor/refilldiscovery/SKILL.md: added cross-reference to buy-inference skillobolup.sh: Docker image fallback for openclaw CLI on npm-deprived environments (ARM64 validated)OASF Taxonomy (validated against agntcy/oasf)
natural_language_processing/natural_language_generation/text_completiontechnology/data_sciencedevops_mlops/model_versioningresearch_and_development/scientific_researchValidation Method
Each milestone was validated with a ralph-loop (5 iterations,
--dangerously-skip-permissions):Known Limitations
OffChainOnlywhen wallet has no Base Sepolia ETHReview Guidance
@OisinKyne — please review:
cmd/obol/sell.gochanges (sell probe + per-hour approximation) — overlaps with your feat: CLI agent-readiness optimizations (#268) #284internal/embed/infrastructure/base/templates/serviceoffer-crd.yaml— new fieldsTest Plan
go build ./... && go test ./...— all passcargo build --release+cargo test --lib(14/14)obolup.shinstall, Docker image builds, full stack up, sell + buy E2E